\CUSProvideExplLibrary{datetime}{\cus@d@te}{\cus@versi@n}{datetime}

\prg_new_conditional:Npnn \cus_if_leap_year:n #1 { p, T, F, TF }
  {
    \int_compare:nNnTF {#1} < { 0 }
      { \exp_args:Nf \__cus_if_leap_year:nTF { \int_eval:n { -1 - \int_eval:n {#1} } } }
      { \exp_args:Nf \__cus_if_leap_year:nTF { \int_eval:n {#1} } }
        { \prg_return_true: } { \prg_return_false: }
  }
\cs_new:Npn \__cus_if_leap_year:nTF #1
  {
    \int_compare:nNnTF { \int_mod:nn {#1} { 4 } } = { 0 }
      {% 整除4
        \int_compare:nNnTF { \int_mod:nn {#1} { 100 } } = { 0 }
          { \exp_args:Nf \__cus_if_leap_year_aux:n { \int_eval:n { #1/100 } } }
          { \use_i:nn } % 不整除100
      }
      { \use_ii:nn } % 不整除4
  }
\cs_new:Npn \__cus_if_leap_year_aux:n #1
  {
    \int_compare:nNnTF { \int_mod:nn {#1} { 4 } } = { 0 }
      {
        \int_compare:nNnTF { \int_mod:nn {#1} { 32 } } = { 0 }
          {
            \int_compare:nNnTF { \int_mod:nn {#1} { 1728 } } = { 0 }
              { \use_i:nn } { \use_ii:nn }
          }
          { \use_i:nn }
      }
      { \use_ii:nn } % 不整除400
  }
\prg_new_conditional:Npnn \cus_if_date:nNn #1 #2 #3 { p, T, F, TF }
  {
    \use:e 
      {
        \exp_not:N \__cus_if_date_aux:wNw 
          \__cus_date_normalize:n {#1} \s__cus_mark #2
          \__cus_date_normalize:n {#3} \s__cus_mark
      }
  }
\cs_new:Npn \__cus_if_date_aux:wNw #1/#2/#3\s__cus_mark #4 #5/#6/#7\s__cus_mark
  {
    \cs_if_exist_use:cTF { __cus_if_date_#4:nnnnnn }
      { {#1} {#2} {#3} {#5} {#6} {#7} }
      {
        \msg_expandable_error:nnn { cus } { datetime-unknown-cmp } {#4} 
        \prg_return_false:
      }
  }
\prg_new_conditional:Npnn \cus_if_date:nnnNnnn #1#2#3 #4 #5#6#7 { p, T, F, TF }
  {
    \exp_args:Nne
    \cs_if_exist_use:cTF { __cus_if_date_#4:nnnnnn }
      {
        { \int_eval:n {#1} } { \int_eval:n {#2} } { \int_eval:n {#3} }
        { \int_eval:n {#5} } { \int_eval:n {#6} } { \int_eval:n {#7} }
      }
      {
        \msg_expandable_error:nnn { cus } { datetime-unknown-cmp } {#4} 
        \prg_return_false:
      }
  }
\cs_new:cpn { __cus_if_date_<:nnnnnn } #1#2#3 #4#5#6
  {
    \if_int_odd:w 0
        \if_int_compare:w #1 < #4~ 1
        \else:
          \if_int_compare:w #1 > #4~ 0
          \else:
            \if_int_compare:w #2 < #5~ 1
            \else:
              \if_int_compare:w #2 > #5~ 0
              \else:
                \if_int_compare:w #3 < #6~ 1 \fi:
              \fi:
            \fi:
          \fi:
        \fi:
      \exp_stop_f: \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\cs_new:cpn { __cus_if_date_=:nnnnnn } #1#2#3 #4#5#6
  {
    \if_int_odd:w 0
        \if_int_compare:w #1 = #4 \exp_stop_f:
          \if_int_compare:w #2 = #5 \exp_stop_f:
            \if_int_compare:w #3 = #6 \exp_stop_f: 1 \fi:
          \fi:
        \fi:
      \exp_stop_f: \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\cs_new:cpn { __cus_if_date_>:nnnnnn } #1#2#3 #4#5#6
  {
    \if_int_odd:w 0
        \if_int_compare:w #1 > #4~ 1
        \else:
          \if_int_compare:w #1 < #4~ 0
          \else:
            \if_int_compare:w #2 > #5~ 1
            \else:
              \if_int_compare:w #2 < #5~ 0
              \else:
                \if_int_compare:w #3 > #6~ 1 \fi:
              \fi:
            \fi:
          \fi:
        \fi:
      \exp_stop_f: \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\cs_new:Npn \__cus_year_c:n #1
  {

  }
\cs_new:Npn \__cus_year_c:f #1 { \exp_args:Nf \__cus_year_c:n { \int_eval:n {#1} } }
\cs_new:Npn \__cus_year_y:f #1 { \exp_args:Nf \__cus_year_y:n { \int_eval:n {#1} } }
% compute weekday, Monday=1, ..., Sunday=7
\cs_new:Npn \cus_weekday:nnn #1#2#3
  {
    \cus_if_date:nnnNnnnTF {#1} {#2} {#3} > { }
  }

\prop_new_linked:N \c_cus_current_datetime_prop
\cs_new:Npn \__cus_timestamp_parse:e #1 % len(year)=4
  { 
    \elkernel_exp:w 
      { \exp_last_unbraced:Ne \__cus_timestamp_parse_a:w {#1} \s__cus_stop } 
  }
\cs_new:Npn \__cus_timestamp_parse_a:w #1#2 #3#4#5#6 
  { 
    { #3#4#5#6 } % year
    \__cus_timestamp_parse_b:w 
  }
\cs_new:Npn \__cus_timestamp_parse_b:w #1#2 #3#4
  {
    { #1#2 } % month
    { #3#4 } % day
    \__cus_timestamp_parse_c:w
  }
\cs_new:Npn \__cus_timestamp_parse_c:w #1#2 #3#4 #5#6
  {
    { #1#2 } % hour
    { #3#4 } % minite
    { #5#6 } % seconds
    \__cus_timestamp_parse_d:w 
  }
\cs_new:Npn \__cus_timestamp_parse_d:w #1 \s__cus_stop
  {
    \if:w Z #1
      \exp_after:wN \__cus_timestamp_parse_e:w
    \else:
      \exp_after:wN \__cus_timestamp_parse_f:w 
    \fi: 
    #1 \s__cus_stop
  }
\cs_new:Npx \__cus_timestamp_parse_e:w #1 \s__cus_stop 
  { { \token_to_str:N Z } { \tl_to_str:n { 00 } } { \tl_to_str:n { 00 } } }
\cs_new:Npn \__cus_timestamp_parse_f:w #1 #2' #3' #4 \s__cus_stop { {#1} {#2} {#3} }

\cs_set:Npn \__cus_tmp:w #1#2#3#4#5#6#7#8#9 
  {
    \token_if_eq_charcode:NNTF #7 Z
      {
        \prop_gput_from_keyval:Nn \c_cus_current_datetime_prop
          {
            year=#1, month=#2, day=#3, hour=#4, minite=#5, seconds=#6,
            zone=+, zonehour=#8, zoneminite=#9,
            raw={#1}{#2}{#3}{#4}{#5}{#6}{+}{#8}{#9}
          }
      }
      {
        \prop_gput_from_keyval:Nn \c_cus_current_datetime_prop
          {
            year=#1, month=#2, day=#3, hour=#4, minite=#5, seconds=#6,
            zone=#7, zonehour=#8, zoneminite=#9,
            raw={#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}
          }
      }
  }
\exp_last_unbraced:Ne \__cus_tmp:w { \__cus_timestamp_parse:e { \c_sys_timestamp_str } }

\cs_new:Npn \__cus_datetime_style:e #1 
  {
    \cs_if_exist_use:cF { __cus_datetime_style/#1 }
      { \msg_expandable_error:nnn { cus } { datetime-unknown-style } {#1} }
  }
\cs_new_protected:Npn \__cus_datetime_set_style:ee #1#2
  { 
    \group_begin:
    \cs_set_eq:NN \# \c_hash_str
    \cs_set_eq:NN \$ \c_dollar_str
    \cs_set_eq:NN \% \c_percent_str
    \cs_set_eq:NN \  \c__cus_other_space_tl
    \cs_set_eq:NN \\ \c_backslash_str
    \cs_set_eq:NN \{ \c_left_brace_str
    \cs_set_eq:NN \} \c_right_brace_str
    \cs_set_eq:NN \^ \c_ampersand_str
    % \cs_set_eq:NN \@ \__cus_datetime_special:w
    \cus_use:e { \group_end: \__cus_datetime_set_style_aux:nn {#1} {#2} }
  }
\cs_new_protected:Npn \__cus_datetime_special:w 
  { }
\cs_new_protected:Npn \__cus_datetime_set_style_aux:nn #1#2
  {
    \str_set:cn { __cus_datetime_style/#1 } {#2}
  }
\__cus_datetime_set_style:ee { full } { \%+ }
\__cus_datetime_set_style:ee { date } { \%F }
\__cus_datetime_set_style:ee { time } { \%T }
\__cus_datetime_set_style:ee { datetime } { \%F\ \%T }

\cs_new:Npn \__cus_datetime_exp_format:Ne #1#2
  {
    \use:e
      { \__cus_datetime_exp_format_aux:NN #1 #2 \q__cus_mark \q__cus_stop }
  }
\group_begin:
\char_set_catcode_other:N \%
\cs_new:Npn \__cus_datetime_exp_format_aux:NN #1#2
  {
    \__cus_quark_if_mark:NTF #2 { \__cus_use_none_delimit_by_q_mark:w }
      {
        \token_if_eq_meaning:NNTF % #2
          { \__cus_datetime_exp_format_spec:NN #1 }
          { #2 \__cus_datetime_exp_format_aux:NN }
      }
}
\group_end:
\cs_new:Npn \__cus_datetime_exp_format_spec:NN #1#2
  {
    \__cus_datetime_exp_format_spec_chk:N #2
    \cs_if_exist_use:cTF { __cus_datetime_exp_format_spec_#2:N } 
      { #1 }
      { \msg_expandable_error:nn { cus } { datetime-spec-error } }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_chk:N #1
  {
    \__cus_quark_if_mark:NTF #1
      {
        \msg_expandable_error:nn { cus } { datetime-spec-error }
        \__cus_use_none_delimit_by_q_stop:w
      }
      { }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_Y:N #1
  { 
    \__cus_datetime_exp_format_spec_zerofill:en 
      { \prop_item:Nn #1 { year } } { 4 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_C:N #1
  {
    \__cus_datetime_exp_format_spec_zerofill:en
      { \__cus_year_c:f { \prop_item:Nn #1 { year } } }
      { 2 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_y:N #1
  {
    \__cus_datetime_exp_format_spec_zerofill:en
      { \__cus_year_y:f { \prop_item:Nn #1 { year } } }
      { 2 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_m:N #1
  {
    \__cus_datetime_exp_format_spec_zerofill:en 
      { \prop_item:Nn #1 { month } } { 2 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_b:N #1
  { \__cus_datetime_exp_format_month_abbr:e { \prop_item:Nn #1 { month } } }
\cs_new:Npn \__cus_datetime_exp_format_spec_B:N #1
  { \__cus_datetime_exp_format_month_full:e { \prop_item:Nn #1 { month } } }
\cs_new:Npn \__cus_datetime_exp_format_spec_h:N #1
  { \__cus_datetime_exp_format_spec:NN #1 b }
\cs_new:Npn \__cus_datetime_exp_format_spec_d:N #1
  {
    \__cus_datetime_exp_format_spec_zerofill:en 
      { \prop_item:Nn #1 { day } } { 2 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_e:N #1
  { \__cus_datetime_exp_format_spec:NN #1 _ d }
\cs_new:Npn \__cus_datetime_exp_format_spec_a:N #1
  {
    \__cus_datetime_exp_format_weekend_abbr:e
      { \__cus_datetime_exp_format_spec_u_aux:N #1 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_A:N #1
  {
    \__cus_datetime_exp_format_weekend_full:e
      { \__cus_datetime_exp_format_spec_u_aux:N #1 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_w:N #1
  {
    \int_mod:nn { \__cus_datetime_exp_format_spec_u_aux:N #1 } { 7 }
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_u:N #1
  {
    \__cus_datetime_exp_format_spec_u_aux:N #1
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_U:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_G:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_g:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_V:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_j:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_D:N #1
  {
    \__cus_datetime_exp_format_spec:NN #1 m /
    \__cus_datetime_exp_format_spec:NN #1 d /
    \__cus_datetime_exp_format_spec:NN #1 y
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_x:N #1
  {

  }
\cs_new:Npn \__cus_datetime_exp_format_spec_F:N #1
  {
    \__cus_datetime_exp_format_spec:NN #1 Y -
    \__cus_datetime_exp_format_spec:NN #1 m -
    \__cus_datetime_exp_format_spec:NN #1 d -
  }
\cs_new:Npn \__cus_datetime_exp_format_spec_v:N #1
  {
    \__cus_datetime_exp_format_spec:NN #1 e -
    \__cus_datetime_exp_format_spec:NN #1 b -
    \__cus_datetime_exp_format_spec:NN #1 Y
  }


\NewExpandableDocumentCommand \formatcurrentdatetime { O{full} m }
  {
    \__cus_datetime_exp_format:Ne \c_cus_current_datetime_prop
      { \tl_if_blank:nTF {#2} { \__cus_datetime_style:e {#1} } {#2} }
  }

\msg_new:nnn { cus } { datetime-unknown-cmp } { Unknown-datetime~operator~`#1'. }
\msg_new:nnn { cus } { datetime-unknown-style } { Unknown~datetime~style~`#1'. }
\msg_new:nnn { cus } { datetime-spec-error } { Illigal~datetime~specifiers. }

